package com.medusa.aps.business.modules.demand.service.impl;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.medusa.aps.business.common.mp.IManualTransaction;
import com.medusa.aps.business.common.redis.RedisUtil;
import com.medusa.aps.business.common.redis.annotation.Redisson;
import com.medusa.aps.business.common.security.resource.helper.ISecurity;
import com.medusa.aps.business.global.model.exception.GlobalException;
import com.medusa.aps.business.model.constant.CommonConstants;
import com.medusa.aps.business.model.constant.RedisConstants;
import com.medusa.aps.business.model.enums.LabelStatus;
import com.medusa.aps.business.model.enums.SchedulingStatusEnum;
import com.medusa.aps.business.modules.basic.mp.entity.Material;
import com.medusa.aps.business.modules.basic.mp.service.MaterialService;
import com.medusa.aps.business.modules.board.task.WorkshopProductionStatisticsTask;
import com.medusa.aps.business.modules.demand.model.input.PlanningOperationParam;
import com.medusa.aps.business.modules.demand.mp.entity.GlobalSettingsForScheduling;
import com.medusa.aps.business.modules.demand.mp.entity.OrderInformation;
import com.medusa.aps.business.modules.demand.mp.entity.PlanOperationProgress;
import com.medusa.aps.business.modules.demand.mp.service.GlobalSettingsForSchedulingService;
import com.medusa.aps.business.modules.demand.mp.service.OrderInformationService;
import com.medusa.aps.business.modules.demand.service.MrpOperationService;
import com.medusa.aps.business.modules.demand.service.MrpPlanningOperationService;
import com.medusa.aps.business.modules.demand.service.PlanningOperationService;
import com.medusa.aps.business.modules.plan.mp.entity.ProcessDetails;
import com.medusa.aps.business.modules.plan.mp.entity.PurchasePlan;
import com.medusa.aps.business.modules.plan.mp.entity.SchedulingTaskView;
import com.medusa.aps.business.modules.plan.mp.entity.SuggestedPlan;
import com.medusa.aps.business.modules.plan.mp.service.ProcessDetailsService;
import com.medusa.aps.business.modules.plan.mp.service.PurchasePlanService;
import com.medusa.aps.business.modules.plan.mp.service.SchedulingTaskViewService;
import com.medusa.aps.business.modules.plan.mp.service.SuggestedPlanService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.time.Duration;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author: xh.yu
 * @createTime: 2023/10/09 13:21
 */
@Service("mrpPlanningOperationService")
@Slf4j
public class MrpPlanningOperationServiceImpl implements MrpPlanningOperationService {
    @Resource
    private PlanningOperationService planningOperationService;
    @Resource
    private OrderInformationService orderInformationService;
    @Resource
    private MrpOperationService mrpOperationService;
    @Resource
    private GlobalSettingsForSchedulingService globalSettingsForSchedulingService;
    @Resource
    private PurchasePlanService purchasePlanService;
    @Resource
    private WorkshopProductionStatisticsTask workshopProductionStatisticsTask;
    @Resource
    private ProcessDetailsService processDetailsService;
    @Resource
    private SuggestedPlanService suggestedPlanService;
    @Resource
    private SchedulingTaskViewService schedulingTaskViewService;
    @Resource
    private MaterialService materialService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Redisson(name = RedisConstants.MRP_PLANNING_OPERATION_LOCK_KEY, waitTime = 1)
    @Redisson(name = RedisConstants.BOM_LOCK_KEY, waitTime = 1)
    @Redisson(name = RedisConstants.MATERIAL_LOCK_KEY, waitTime = 1)
    @Redisson(name = RedisConstants.PROCESS_ROUTE_LOCK_KEY, waitTime = 1)
    @Redisson(name = RedisConstants.CLASS_SYSTEM_LOCK_KEY, waitTime = 1)
    public void mrpPlaningOperation(Long id, PlanningOperationParam planningOperationParam) throws Exception {
        PlanOperationProgress planOperationProgress = PlanOperationProgress.builder()
                .id(id)
                .rate(BigDecimal.ZERO)
                .status(CommonConstants.SUCCESS)
                .orderIds(new HashSet<>())
                .creator(ISecurity.userMust().getUsername())
                .build();
        String key = RedisUtil.key(RedisConstants.PLANING_OPERATION, id);
        RedisUtil.setCacheMap(key, planOperationProgress, Duration.ofSeconds(RedisConstants.PLANING_EXPIRE_TIME));
        List<OrderInformation> orderInformationList = getOrderInformations(planningOperationParam, planOperationProgress);
        if (!CollectionUtils.isEmpty(orderInformationList)) {
            //MRP运算
            mrpOperationService.mrpOperation(orderInformationList, planOperationProgress);
            //计划运算
            planningOperationService.planingOperation(planOperationProgress);
        }
        productionStatistics();
    }

    /**
     * 查询并校验订单信息
     *
     * @return 订单信息
     */
    @Nullable
    private List<OrderInformation> getOrderInformations(PlanningOperationParam planningOperationParam, PlanOperationProgress planOperationProgress) {
        return switch (planningOperationParam.getPlanningOperationType()) {
            case ALL_OPERATION -> allOperation();
            case CANCEL_OPERATION -> cancelOperation(planningOperationParam, planOperationProgress);
            case ORDER_INSERT_OPERATION, PARTIAL_OPERATION, AGAIN_OPERATION, TERMINATE_OPERATION -> null;
        };
    }

    /**
     * 重新触发设备车间排程数据统计
     */
    private void productionStatistics() {
        IManualTransaction.afterCommit(() -> workshopProductionStatisticsTask.statistics());
    }

    /**
     * 全部运算
     *
     * @return 排程订单
     */
    private List<OrderInformation> allOperation() {
        List<OrderInformation> orderInformationList = orderInformationService.selectByScheduled(List.of(SchedulingStatusEnum.NOTARRANGED.getCode()), new HashSet<>());
        GlobalSettingsForScheduling globalSettingsForScheduling = globalSettingsForSchedulingService.getOne(new LambdaQueryWrapper<>());
        if (!CollectionUtils.isEmpty(orderInformationList) && globalSettingsForScheduling != null && globalSettingsForScheduling.getSchedulingEndTime() != null) {
            List<OrderInformation> orderInformations = orderInformationList.stream().filter(o -> o.getOrderDeliveryDate().isAfter(globalSettingsForScheduling.getSchedulingEndTime())).toList();
            if (!CollectionUtils.isEmpty(orderInformations) && orderInformationList.size() == orderInformations.size()) {
                throw new GlobalException("所有订单交期时间均晚于排程结束时间，请调整排程结束时间或调整订单！");
            }
            orderInformationList = orderInformationList.stream().filter(o -> !o.getOrderDeliveryDate().isAfter(globalSettingsForScheduling.getSchedulingEndTime())).toList();
        }
        if (CollectionUtils.isEmpty(orderInformationList)) {
            throw new GlobalException("没有查询到需要排程的订单！");
        }
        return orderInformationList;
    }

    /**
     * 取消排程
     *
     * @param planningOperationParam 计划排程参数对象
     * @param planOperationProgress  计划运算进度model
     * @return 排程订单
     */
    private List<OrderInformation> cancelOperation(PlanningOperationParam planningOperationParam, PlanOperationProgress planOperationProgress) {
        Set<String> orderNumber = planningOperationParam.getOrderNumber();
        if (CollectionUtils.isEmpty(orderNumber)) {
            throw new GlobalException("请选择订单!");
        }
        List<SchedulingStatusEnum> schedulingStatusEnums = List.of(SchedulingStatusEnum.ARRANGED);
        checkOrderStatus(orderNumber, schedulingStatusEnums, planOperationProgress);
        Set<LabelStatus> labelStatus = new HashSet<>();
        updateOrderLabel(orderNumber, labelStatus);
        Set<String> arrangedOrderNumbers = getArrangedOrderNumbers();
        removeArrangedOrderRelationInfo(arrangedOrderNumbers);
        orderInformationService.lambdaUpdate()
                .in(OrderInformation::getOrderNumber, orderNumber)
                .set(OrderInformation::getSchedulingStatus, SchedulingStatusEnum.NOTARRANGED)
                .set(OrderInformation::getIsWaitArrange, Boolean.FALSE)
                .set(OrderInformation::getPlannedStartTime, null)
                .set(OrderInformation::getPlannedEndTime, null)
                .update();
        if (planningOperationParam.getSynDeleteOrder()) {
            orderInformationService.lambdaUpdate()
                    .in(OrderInformation::getOrderNumber, orderNumber)
                    .remove();
        }
        arrangedOrderNumbers.removeAll(orderNumber);
        if (arrangedOrderNumbers.isEmpty()) {
            planOperationProgress.setRate(new BigDecimal(100));
            updatePlanOperationProgress(planOperationProgress);
            return Collections.emptyList();
        }
        return orderInformationService.selectByScheduled(List.of(SchedulingStatusEnum.ARRANGED.getCode()), arrangedOrderNumbers);
    }

    /**
     * 检查订单状态
     *
     * @param orderNumber           订单号
     * @param schedulingStatusEnums 订单状态集合
     */
    private void checkOrderStatus(Set<String> orderNumber, List<SchedulingStatusEnum> schedulingStatusEnums, PlanOperationProgress planOperationProgress) {
        Set<String> orderNos = orderInformationService.lambdaQuery()
                .select(OrderInformation::getOrderNumber)
                .in(OrderInformation::getOrderNumber, orderNumber)
                .in(OrderInformation::getSchedulingStatus, schedulingStatusEnums)
                .list().stream().map(OrderInformation::getOrderNumber).collect(Collectors.toSet());
        if (orderNos.isEmpty()) {
            planOperationProgress.setOrderIds(orderNumber);
            updatePlanOperationProgress(planOperationProgress);
            throw new GlobalException("订单状态不符合要求!");
        }
        if (orderNumber.size() != orderNos.size()) {
            orderNumber.removeAll(orderNos);
            planOperationProgress.setOrderIds(orderNumber);
            updatePlanOperationProgress(planOperationProgress);
            throw new GlobalException("订单状态不符合要求!");
        }
    }

    /**
     * 更新订单标签
     *
     * @param orderNumber 订单号
     * @param labelStatus 标签
     */
    private void updateOrderLabel(Set<String> orderNumber, Set<LabelStatus> labelStatus) {
        orderInformationService.lambdaUpdate()
                .in(OrderInformation::getOrderNumber, orderNumber)
                .set(OrderInformation::getLabels, JSONUtil.toJsonStr(labelStatus))
                .update();
    }

    /**
     * 查询已排程订单号
     *
     * @return 订单号
     */
    @NotNull
    private Set<String> getArrangedOrderNumbers() {
        return orderInformationService.lambdaQuery().select(OrderInformation::getOrderNumber)
                .eq(OrderInformation::getSchedulingStatus, SchedulingStatusEnum.ARRANGED)
                .list().stream().map(OrderInformation::getOrderNumber).collect(Collectors.toSet());
    }

    /**
     * 删除已排程订单相关信息
     *
     * @param arrangedOrderNumbers 已排程订单号
     */
    private void removeArrangedOrderRelationInfo(Set<String> arrangedOrderNumbers) {
        if (CollectionUtils.isEmpty(arrangedOrderNumbers)) {
            return;
        }
        updateOrderInfo(arrangedOrderNumbers);
        processDetailsService.lambdaUpdate()
                .in(ProcessDetails::getOrderNumber, arrangedOrderNumbers)
                .remove();
        purchasePlanService.lambdaUpdate()
                .in(PurchasePlan::getOrderNumber, arrangedOrderNumbers)
                .remove();
        revertMaterial(arrangedOrderNumbers);
        suggestedPlanService.lambdaUpdate()
                .in(SuggestedPlan::getOrderNumber, arrangedOrderNumbers)
                .remove();
        schedulingTaskViewService.lambdaUpdate()
                .in(SchedulingTaskView::getOrderNumber, arrangedOrderNumbers)
                .remove();
    }

    /**
     * 归还物料
     *
     * @param arrangedOrderNumbers 已排程订单号
     */
    private void revertMaterial(Set<String> arrangedOrderNumbers) {
        List<SuggestedPlan> suggestedPlanList = suggestedPlanService.query()
                .select("material_code", "sum(possession_inventory) as possession_inventory")
                .in("order_number", arrangedOrderNumbers)
                .isNotNull("possession_inventory")
                .groupBy("material_code").list();
        revertMaterial(suggestedPlanList);
    }

    /**
     * 归还物料
     *
     * @param suggestedPlanList 建议计划
     */
    private void revertMaterial(List<SuggestedPlan> suggestedPlanList) {
        if (CollectionUtils.isEmpty(suggestedPlanList)) {
            return;
        }
        suggestedPlanList = suggestedPlanList.stream().filter(s -> s.getPossessionInventory().intValue() != 0).toList();
        if (CollectionUtils.isEmpty(suggestedPlanList)) {
            return;
        }
        suggestedPlanList.forEach(suggestedPlan -> {
            Material material = materialService.lambdaQuery()
                    .eq(Material::getMaterialCode, suggestedPlan.getMaterialCode())
                    .one();
            if (material != null) {
                material.setActualInventory(material.getActualInventory().add(suggestedPlan.getPossessionInventory()));
                materialService.updateById(material);
            }
        });
    }

    /**
     * 更新已排程订单
     *
     * @param arrangedOrderNumbers 已排程订单号
     */
    private void updateOrderInfo(Set<String> arrangedOrderNumbers) {
        orderInformationService.lambdaUpdate()
                .in(OrderInformation::getOrderNumber, arrangedOrderNumbers)
                .set(OrderInformation::getIsWaitArrange, Boolean.FALSE)
                .set(OrderInformation::getPlannedStartTime, null)
                .set(OrderInformation::getPlannedEndTime, null)
                .update();
    }

    /**
     * 更新排程进度
     *
     * @param planOperationProgress 排程进度model
     */
    private void updatePlanOperationProgress(PlanOperationProgress planOperationProgress) {
        String key = RedisUtil.key(RedisConstants.PLANING_OPERATION, planOperationProgress.getId());
        RedisUtil.setCacheMap(key, planOperationProgress, Duration.ofSeconds(RedisConstants.PLANING_EXPIRE_TIME));
    }

    /**
     * 更新计划运算进度
     *
     * @param id            计划进度id
     * @param failureReason 失败原因
     */
    @Override
    public void updatePlanOperationProgress(Long id, String failureReason) {
        String key = RedisUtil.key(RedisConstants.PLANING_OPERATION, id);
        PlanOperationProgress planOperationProgress = RedisUtil.getCacheMap(key, PlanOperationProgress.class);
        if (planOperationProgress != null) {
            planOperationProgress.setStatus(CommonConstants.FAIL);
            planOperationProgress.setFailureReason(failureReason);
            RedisUtil.setCacheMap(key, planOperationProgress, Duration.ofSeconds(RedisConstants.PLANING_EXPIRE_TIME));
        }
    }

    /**
     * 获取计划运算进度
     *
     * @param id 计划运算进度id
     * @return 计划运算进度
     */
    @Override
    public PlanOperationProgress planOperationProgress(long id) {
        String key = RedisUtil.key(RedisConstants.PLANING_OPERATION, id);
        PlanOperationProgress planOperationProgress = RedisUtil.getCacheMap(key, PlanOperationProgress.class);
        if (planOperationProgress == null) {
            planOperationProgress = PlanOperationProgress.builder()
                    .id(id)
                    .rate(BigDecimal.ZERO)
                    .status(CommonConstants.FAIL)
                    .orderIds(new HashSet<>())
                    .build();
        }
        return planOperationProgress;
    }

    @Override
    public String checkPurchasePlan(Set<String> orderNumbers) {
        Set<String> purchasePlanOrderNumbers = purchasePlanService.lambdaQuery().select(PurchasePlan::getOrderNumber)
                .in(PurchasePlan::getOrderNumber, orderNumbers)
                .list().stream().map(PurchasePlan::getOrderNumber).collect(Collectors.toSet());
        if (purchasePlanOrderNumbers.isEmpty()) {
            return null;
        }
        return StringUtils.collectionToDelimitedString(purchasePlanOrderNumbers, "、");
    }
}
